Axel Fridman

L.U : 527/20

Buscamos primero cargar todos los datos.

install.packages("plotly")
Error in install.packages : Updating loaded packages
install.packages("rgl")
Error in install.packages : Updating loaded packages
install.packages("plot3D")
Error in install.packages : Updating loaded packages
install.packages(tidyverse)
Error in install.packages : object 'tidyverse' not found
install.packages(dplyr)
Error in install.packages : object 'dplyr' not found
install.packages(GGally)
Error in install.packages : object 'GGally' not found

library("plotly")
library("plot3D")
library(tidyverse) # entorno tidy
library(dplyr) # manejo de datos
library(GGally) # scatterplots multiples
library(rgl) # para graficos 3D
df = read.csv("datos_alquiler.csv", stringsAsFactors = F)

Vemos todos los atributos de cada propiedad

colnames(df)
 [1] "id"              "start_date"      "lat"             "lon"             "l1"              "l2"              "rooms"           "surface_total"  
 [9] "surface_covered" "price"           "currency"        "property_type"   "operation_type"  "fondo"          
table(df$property_type)

        Casa Departamento           PH 
          27         1828           90 
mayor = quantile(df$surface_total, 0.99)
label = "Superficies "
hist(df$surface_total, breaks = 100,  freq = F, xlab = label, xlim = c(0,mayor), main ="")

Hay un pico alrededor de 40-50m2 y luego un lento descenso de cantidad de casas con mayor superficie. Quite las 1% mas espaciosas.

variables_Interes = c("price", "rooms", "surface_total", "fondo", "lat", "lon")
labelsInteres = c("Precio", "Habitaciones", "Superficie total", "Fondo", "lat", "lon")
variables_Interes2 = c("property_type", "start_date")
par(mfrow=c(2,4))
breaksVector = c(40,20,50,35,20,20)
plot(df[,variables_Interes[5]], df[,variables_Interes[6]], xlab = "Longitud", ylab = "Latitud")
for (i in 1:length(variables_Interes)) {
  #mayor = quantile(df[,variables_Interes[i]], 1, na.rm=T)
  label = labelsInteres[i]
  hist(df[,variables_Interes[i]], breaks = breaksVector[i],  freq = F, xlab = label, main ="")
}
with(df,{
  
tiposProp = table(property_type)
prop.table(table(tiposProp))
barplot(tiposProp, las=2, cex.names=0.65)
})

Parece haber 3 zonas muy distintas. La mayoria de las casas tienen entre 1 y 3 habitaciones. Estamos hablando de analizar un dataset con una absoluta mayoria de departamentos en vez de casas y phs.

df$start_date = as.Date(df$start_date)
df$diaEsp = factor(weekdays(df$start_date), levels = c("lunes", "martes", "miércoles", "jueves", "viernes","sábado", "domingo"))
df$mesEsp = factor(months(df$start_date), levels= c("enero", "febrero", "marzo", "junio", "julio","agosto", "septiembre", "octubre", "noviembre", "diciembre"))
df$diaIng = factor(weekdays(df$start_date), levels = c("Monday", "Tuesday","Wednesday", "Thursday", "Friday", "Saturday","Sunday"))
df$mesIng = factor(months(df$start_date), levels= c("January", "February", "March","April","May", "June", "July","August", "September", "October", "November", "December"))

with(df,{
par(mfrow=c(1,2))

dias = table(diaEsp) #Poner diaIng o diaEsp segun corresponda
prop.table(table(dias))
barplot(dias, las=2, cex.names=0.65)
meses = table(mesEsp) #Poner mesIng o mesEsp segun corresponda
prop.table(table(meses))
barplot(meses, las=2, cex.names=0.65, )
})

ajusM1<-lm(price~1,data=df)
coe<-coef(ajusM1)
plot(df$price,  xlab = "Propiedades", ylab = "Precio")
abline(h = coe[1], col='red')
abline(h = quantile(df$price, 0.95), col='blue')

#h = lm(id ~ -1, data=df)

Como la linea roja viene de un modelo con un parametro constante que no depende de ninguna variable explicativa es el promedio (con loss function suma de diferencia de cuadrados). Podemos ver que la mayoria de las propiedades (el 95%) estan concentradas por debajo de la linea azul. Mientras que el 5% de propiedades mas caras empujan el promedio (linea roja) ligeramente arriba del centro de la nube negra de propiedades. Es decir el 5% de propiedades mas caras ocupa mas de la mitad del grafico.

with(df,{
par(mfrow=c(1,3))
  
plot(price~surface_covered, xlab ='Superficie cubierta', ylab = 'Precio')
precioSupC<-lm(price~surface_covered,data=df)
coeSP<-coef(precioSupC)
abline(coeSP, col = 'red')

plot(price~fondo, xlab ='Fondo', ylab = 'Precio')
precioFon<-lm(price~fondo,data=df)
coeFP<-coef(precioFon)
abline(coeFP, col = 'red')

plot(price~start_date, xlab ='Fecha de pub', ylab = 'Precio')
precioFecha<-lm(price~start_date,data=df)
coeFech<-coef(precioFecha)
abline(coeFech, col = 'red')
})

A los datos me remito, es muchisimo mas clara la relacion entre superficie cubierta y precio, que precio y fondo. Ni que hablar fecha de publicacion, en el cual se ve que mi coeficiente de grado 1 es practicamente 0. Con lo cual obtenemos algo similar al promedio de fecha de publicacion.

precioSupC<-lm(price~surface_covered,data=df)
precioFecha<-lm(price~start_date,data=df)
precioFon<-lm(price~fondo,data=df)

summary(precioSupC)

Call:
lm(formula = price ~ surface_covered, data = df)

Residuals:
   Min     1Q Median     3Q    Max 
-20670  -2470   -581   1833  40857 

Coefficients:
                Estimate Std. Error t value Pr(>|t|)    
(Intercept)     5168.986    244.641   21.13   <2e-16 ***
surface_covered  231.388      4.212   54.93   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 4579 on 1943 degrees of freedom
Multiple R-squared:  0.6083,    Adjusted R-squared:  0.6081 
F-statistic:  3018 on 1 and 1943 DF,  p-value: < 2.2e-16
summary(precioFon)

Call:
lm(formula = price ~ fondo, data = df)

Residuals:
   Min     1Q Median     3Q    Max 
-18612  -4544  -1935   2543  47412 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 16544.01     181.19   91.31   <2e-16 ***
fondo         130.45      13.35    9.77   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 7142 on 1943 degrees of freedom
Multiple R-squared:  0.04682,   Adjusted R-squared:  0.04633 
F-statistic: 95.45 on 1 and 1943 DF,  p-value: < 2.2e-16
summary(precioFecha)

Call:
lm(formula = price ~ start_date, data = df)

Residuals:
   Min     1Q Median     3Q    Max 
 -9575  -5049  -2136   2648  47722 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)
(Intercept) -40616.288  59867.212  -0.678    0.498
start_date       3.223      3.329   0.968    0.333

Residual standard error: 7314 on 1943 degrees of freedom
Multiple R-squared:  0.0004821, Adjusted R-squared:  -3.235e-05 
F-statistic: 0.9371 on 1 and 1943 DF,  p-value: 0.3331

El R de cada modelo esta ordenado como el grafico. Superf cub > fondo > fecha de pub Notado que de los residuos, el mediano es siempre negativo, puedo deducir que el modelo esta prediciendo que el elemento del medio deberia ser mas grande que el que es. Esto puede deberse a la influencia de lo mucho mayor que es el precio cuando la superficie es mas grande, lo cual podria deberse a que el modelo lineal no se ajusta del todo bien por como estan distribuidos los precios en relacion a la sup.

summary(precioSupC$residuals)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-20669.9  -2469.8   -581.5      0.0   1833.1  40857.2 
summary(precioFon$residuals)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 -18612   -4544   -1935       0    2543   47412 
summary(precioFecha$residuals)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  -9575   -5049   -2136       0    2648   47722 
#sqrt(sum((precioSupC$residuals)**2))
plot(df$surface_covered, df$price, col='red', ylim = c(0 , 1.5*max(predict(precioSupC))), xlab = 'Superficie cubierta', ylab = 'Precio')
points( df$surface_covered, predict(precioSupC), col='blue')

La prediccion en azul mientras que los datos en rojo. Se puede ver como la ‘dispersion’ de los precios de superficie mas baja es menor, entonces uno puede tener una mejor prediccion, mientras que si aumenta mucho la superficie la variabilidad del precio es mayor, y el modelo le pifia mas.

desempenioHastaPorcentaje = function(porcentaje){
  sup = quantile(df$surface_covered, porcentaje)
  sups = df[df$surface_covered <= sup,]$surface_covered
  precio = df[df$surface_covered <= sup,]$price
  modelo<-lm(precio~sups,data=df)
  return(summary(lm(precio~sups, data=df))$r.squared)
  
}
porcentajes = c(1:100)/100
plot(sapply(porcentajes, desempenioHastaPorcentaje), xlab = 'Porcentaje del dataset evaluado en modelo', ylab = 'Error del modelo en R cuadrado')

Claramente el error crece raoidamente y “de a saltos” a medida que tomo un mayor porcentaje de los datos.

par(mfrow=c(1,3))
plot(precioSupC$residuals, xlab = 'Residuos superficie cubierta', ylab = 'Residuos')
abline(h=0,col= 'red')
plot(precioFon$residuals, xlab = 'Residuos fondo', ylab = 'Residuos')
abline(h=0,col= 'red')
plot(precioFecha$residuals, xlab = 'Residuos fecha de pub', ylab = 'Residuos')
abline(h=0,col= 'red')

Tal como esperabamos, el promedio del modulo los residuos es mas chico en superifice cubierta (grafico mas comprimido en 0) mientras que va aumentando debido a mayores diferencias al 0 en los otros graficos.

modeloPrecioTiempo = lm(price~start_date, data = df)
plot(df$price~df$start_date, xlab="Fecha", ylab="Precio")
abline(coef(modeloPrecioTiempo), col='red')

No se aprecia la inflacion, es practicamente una constante.

modeloPrecioTipoPropiedad = lm(price~property_type, data = df)
boxplot(df$price~df$property_type)
coefProp = coef(modeloPrecioTipoPropiedad)
abline(modeloPrecioTipoPropiedad)
Warning in abline(modeloPrecioTipoPropiedad) :
  only using the first two of 3 regression coefficients

print(coefProp)
              (Intercept) property_typeDepartamento           property_typePH 
                22981.481                 -5852.160                 -3103.704 

Lo que este modelo nos dice es que las casas por el simple hecho de ser casas tienen un valor promedio de 22981 (que actua como una ‘base’ para el resto de las propiedades. Mientras que ser un departamento te penaliza por -5852 menos el precio relativo a una casa y ser un PH tambien te penaliza por -3103. El modelo que hice solamente conecta precio de Casa vs Departamento

zonas = kmeans(df$lat + df$lon, 3, iter.max = 50)
colores = c('red','green', 'blue')
plot(df$lat, df$lon, col=colores[zonas$cluster])

Dividimos por zona segun ubicacion, y nos fijamos que modelo ajusta mejor a cada zona particular


modelo1 = lm(price~surface_covered+rooms, data = df[zonas$cluster==1,])
modelo2 = lm(price~surface_covered+rooms, data = df[zonas$cluster==2,])
modelo3 = lm(price~surface_covered+rooms, data = df[zonas$cluster==3,])

par(mfrow=c(2,3))
plot(df[zonas$cluster==1,]$lat, df[zonas$cluster==1,]$lon, col=colores[1])
plot(df[zonas$cluster==2,]$lat, df[zonas$cluster==2,]$lon, col=colores[2])
plot(df[zonas$cluster==3,]$lat, df[zonas$cluster==3,]$lon, col=colores[3])
scatter3D(df[zonas$cluster==1,]$surface_covered, df[zonas$cluster==1,]$rooms, df[zonas$cluster==1,]$price, colvar = df[zonas$cluster==1,]$price, col = 2, add = F,xlab = 'Sup Cub', ylab= 'Hab', zlab = 'Precio')
scatter3D(df[zonas$cluster==2,]$surface_covered, df[zonas$cluster==2,]$rooms, df[zonas$cluster==2,]$price, colvar = df[zonas$cluster==2,]$price, col = 3, add = F,xlab = 'Sup Cub', ylab= 'Hab', zlab = 'Precio')
scatter3D(df[zonas$cluster==3,]$surface_covered, df[zonas$cluster==3,]$rooms, df[zonas$cluster==3,]$price, colvar = df[zonas$cluster==3,]$price, col = 4, add = F, xlab = 'Sup Cub', ylab= 'Hab', zlab = 'Precio')

#set.seed(417)
#temp <- rnorm(100, mean=30, sd=5)
#pressure <- rnorm(100)
#dtime <- 1:100
#plot_ly(x=df[zonas$cluster==1,]$surface_covered, y=df[zonas$cluster==1,]$rooms, z=df[zonas$cluster==1,]$price, type="scatter3d", mode="markers", color=0.3)

Superficie = df$surface_covered
Habitaciones = df$rooms
Precio = df$price
colores2 = c("IndianRed", "MediumPurple", "DarkOrange")
#color=colores[zonas$cluster]

fig <- plot_ly(x=~Superficie, y=~Habitaciones, z=~Precio, type="scatter3d", mode="markers", marker = list(color = colores[zonas$cluster], showscale = F),xlab = 'Sup Cub', ylab= 'Hab', zlab = 'Precio', size = 1)
fig <- fig %>% layout(title = '3 zonas distribuicion',
         xaxis = list(title = 'Superficie cubierta',
                      zeroline = TRUE,
                      range = c(0, 250)),
         yaxis = list(title = 'Habitaciones',
                      range = c(0,1400)))

fig
Warning: Ignoring 166 observations
Warning: 'scatter3d' objects don't have these attributes: 'xlab', 'ylab', 'zlab'
Valid attributes include:
'connectgaps', 'customdata', 'customdatasrc', 'error_x', 'error_y', 'error_z', 'hoverinfo', 'hoverinfosrc', 'hoverlabel', 'hovertemplate', 'hovertemplatesrc', 'hovertext', 'hovertextsrc', 'ids', 'idssrc', 'legendgroup', 'legendgrouptitle', 'legendrank', 'line', 'marker', 'meta', 'metasrc', 'mode', 'name', 'opacity', 'projection', 'scene', 'showlegend', 'stream', 'surfaceaxis', 'surfacecolor', 'text', 'textfont', 'textposition', 'textpositionsrc', 'textsrc', 'texttemplate', 'texttemplatesrc', 'transforms', 'type', 'uid', 'uirevision', 'visible', 'x', 'xcalendar', 'xhoverformat', 'xsrc', 'y', 'ycalendar', 'yhoverformat', 'ysrc', 'z', 'zcalendar', 'zhoverformat', 'zsrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isGraticule', '_bbox'

Warning: Ignoring 166 observations
Warning: 'scatter3d' objects don't have these attributes: 'xlab', 'ylab', 'zlab'
Valid attributes include:
'connectgaps', 'customdata', 'customdatasrc', 'error_x', 'error_y', 'error_z', 'hoverinfo', 'hoverinfosrc', 'hoverlabel', 'hovertemplate', 'hovertemplatesrc', 'hovertext', 'hovertextsrc', 'ids', 'idssrc', 'legendgroup', 'legendgrouptitle', 'legendrank', 'line', 'marker', 'meta', 'metasrc', 'mode', 'name', 'opacity', 'projection', 'scene', 'showlegend', 'stream', 'surfaceaxis', 'surfacecolor', 'text', 'textfont', 'textposition', 'textpositionsrc', 'textsrc', 'texttemplate', 'texttemplatesrc', 'transforms', 'type', 'uid', 'uirevision', 'visible', 'x', 'xcalendar', 'xhoverformat', 'xsrc', 'y', 'ycalendar', 'yhoverformat', 'ysrc', 'z', 'zcalendar', 'zhoverformat', 'zsrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isGraticule', '_bbox'
modelo1$coefficients
    (Intercept) surface_covered           rooms 
      6313.4677        216.6692        361.9120 
plot3d(modelo1,size=15,col="red")
modelo2$coefficients
    (Intercept) surface_covered           rooms 
      4207.1741        189.3097        318.0363 
plot3d(modelo2,size=15,col="green")
modelo3$coefficients
    (Intercept) surface_covered           rooms 
      3811.1068        237.6054        734.1301 
plot3d(modelo3,size=15,col="blue")

De los coeficientes de cada modelo segun la zona podemos ver que las habitaciones suman mucho mas en una de las 3 zonas. Por otro lado hay otra zona distinta que es mas cara a ‘0 hab y 0 superficie’. En cuanto a cuanto cuesta el m2 cuadrado cubierto en cada zona es similar.

Construya una gráfica que muestre el error de ajuste en función de la cantidad de parámetros de cada modelo. Cuál modelo tiene menor error de ajuste?

modeloSimple0 = lm(price~surface_covered + fondo, data = df)
modeloSimple1 = lm(price~surface_covered +fondo, data = df)
modeloSimple2 = lm(price~surface_covered +fondo+property_type, data = df)
modeloSimple3 = lm(price~surface_covered +fondo+property_type+lat+lon, data = df)
modeloSimple4 = lm(price~surface_covered +fondo+property_type+lat+lon+start_date, data = df)
modeloCompleto = lm(price~surface_covered +fondo+property_type+lat+lon+start_date+rooms, data = df)
coefR = c(summary(modeloSimple0)$r.squared,summary(modeloSimple1)$r.squared,summary(modeloSimple2)$r.squared,summary(modeloSimple3)$r.squared,summary(modeloSimple4)$r.squared,summary(modeloCompleto)$r.squared)
plot(c(1:6), coefR, xlab = 'Modelo', ylab=' Coeficiente R cuadrado')

Podemos ver como progresa el R cuadrado cuando vamos sumando variables explicativas, notese como sumar la fecha de publicacion y habitaciones no cambia practicamente nada, ya que 4 y 5 y 6 son casi identicos. Lo mismo con 1, 2 y 3, property type y fondo no redujeron mucho el error del modelo. Esto nos dice que para minimizar el error de ajuste hay que sumar variables explicativas, pero no cualquiera, ya que en este caso la fecha fue un pesimo indicador. El quiebre se produce cuando consideramos latitud y longitud.

LS0tCnRpdGxlOiAiVFAzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCiMjIEF4ZWwgRnJpZG1hbgojIyBMLlUgOiA1MjcvMjAKCkJ1c2NhbW9zIHByaW1lcm8gY2FyZ2FyIHRvZG9zIGxvcyBkYXRvcy4KYGBge3J9Cmluc3RhbGwucGFja2FnZXMoInBsb3RseSIpCmluc3RhbGwucGFja2FnZXMoInJnbCIpCmluc3RhbGwucGFja2FnZXMoInBsb3QzRCIpCmluc3RhbGwucGFja2FnZXModGlkeXZlcnNlKQppbnN0YWxsLnBhY2thZ2VzKGRwbHlyKQppbnN0YWxsLnBhY2thZ2VzKEdHYWxseSkKYGBgCgoKYGBge3J9CgpsaWJyYXJ5KCJwbG90bHkiKQpsaWJyYXJ5KCJwbG90M0QiKQpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBlbnRvcm5vIHRpZHkKbGlicmFyeShkcGx5cikgIyBtYW5lam8gZGUgZGF0b3MKbGlicmFyeShHR2FsbHkpICMgc2NhdHRlcnBsb3RzIG11bHRpcGxlcwpsaWJyYXJ5KHJnbCkgIyBwYXJhIGdyYWZpY29zIDNECmRmID0gcmVhZC5jc3YoImRhdG9zX2FscXVpbGVyLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpgYGAKClZlbW9zIHRvZG9zIGxvcyBhdHJpYnV0b3MgZGUgY2FkYSBwcm9waWVkYWQKCmBgYHtyfQpjb2xuYW1lcyhkZikKYGBgCmBgYHtyfQp0YWJsZShkZiRwcm9wZXJ0eV90eXBlKQoKbWF5b3IgPSBxdWFudGlsZShkZiRzdXJmYWNlX3RvdGFsLCAwLjk5KQpsYWJlbCA9ICJTdXBlcmZpY2llcyAiCmhpc3QoZGYkc3VyZmFjZV90b3RhbCwgYnJlYWtzID0gMTAwLCAgZnJlcSA9IEYsIHhsYWIgPSBsYWJlbCwgeGxpbSA9IGMoMCxtYXlvciksIG1haW4gPSIiKQpgYGAKSGF5IHVuIHBpY28gYWxyZWRlZG9yIGRlIDQwLTUwbTIgeSBsdWVnbyB1biBsZW50byBkZXNjZW5zbyBkZSBjYW50aWRhZCBkZSBjYXNhcyBjb24gbWF5b3Igc3VwZXJmaWNpZS4gUXVpdGUgbGFzIDElIG1hcyBlc3BhY2lvc2FzLgpgYGB7cn0KdmFyaWFibGVzX0ludGVyZXMgPSBjKCJwcmljZSIsICJyb29tcyIsICJzdXJmYWNlX3RvdGFsIiwgImZvbmRvIiwgImxhdCIsICJsb24iKQpsYWJlbHNJbnRlcmVzID0gYygiUHJlY2lvIiwgIkhhYml0YWNpb25lcyIsICJTdXBlcmZpY2llIHRvdGFsIiwgIkZvbmRvIiwgImxhdCIsICJsb24iKQp2YXJpYWJsZXNfSW50ZXJlczIgPSBjKCJwcm9wZXJ0eV90eXBlIiwgInN0YXJ0X2RhdGUiKQpwYXIobWZyb3c9YygyLDQpKQpicmVha3NWZWN0b3IgPSBjKDQwLDIwLDUwLDM1LDIwLDIwKQpwbG90KGRmWyx2YXJpYWJsZXNfSW50ZXJlc1s1XV0sIGRmWyx2YXJpYWJsZXNfSW50ZXJlc1s2XV0sIHhsYWIgPSAiTG9uZ2l0dWQiLCB5bGFiID0gIkxhdGl0dWQiKQpmb3IgKGkgaW4gMTpsZW5ndGgodmFyaWFibGVzX0ludGVyZXMpKSB7CiAgI21heW9yID0gcXVhbnRpbGUoZGZbLHZhcmlhYmxlc19JbnRlcmVzW2ldXSwgMSwgbmEucm09VCkKICBsYWJlbCA9IGxhYmVsc0ludGVyZXNbaV0KICBoaXN0KGRmWyx2YXJpYWJsZXNfSW50ZXJlc1tpXV0sIGJyZWFrcyA9IGJyZWFrc1ZlY3RvcltpXSwgIGZyZXEgPSBGLCB4bGFiID0gbGFiZWwsIG1haW4gPSIiKQp9CndpdGgoZGYsewogIAp0aXBvc1Byb3AgPSB0YWJsZShwcm9wZXJ0eV90eXBlKQpwcm9wLnRhYmxlKHRhYmxlKHRpcG9zUHJvcCkpCmJhcnBsb3QodGlwb3NQcm9wLCBsYXM9MiwgY2V4Lm5hbWVzPTAuNjUpCn0pCgpgYGAKUGFyZWNlIGhhYmVyIDMgem9uYXMgbXV5IGRpc3RpbnRhcy4KTGEgbWF5b3JpYSBkZSBsYXMgY2FzYXMgdGllbmVuIGVudHJlIDEgeSAzIGhhYml0YWNpb25lcy4gRXN0YW1vcyBoYWJsYW5kbyBkZSBhbmFsaXphciB1biBkYXRhc2V0IGNvbiB1bmEgYWJzb2x1dGEgbWF5b3JpYSBkZSBkZXBhcnRhbWVudG9zIGVuIHZleiBkZSBjYXNhcyB5IHBocy4KYGBge3J9CmRmJHN0YXJ0X2RhdGUgPSBhcy5EYXRlKGRmJHN0YXJ0X2RhdGUpCmRmJGRpYUVzcCA9IGZhY3Rvcih3ZWVrZGF5cyhkZiRzdGFydF9kYXRlKSwgbGV2ZWxzID0gYygibHVuZXMiLCAibWFydGVzIiwgIm1pw6lyY29sZXMiLCAianVldmVzIiwgInZpZXJuZXMiLCJzw6FiYWRvIiwgImRvbWluZ28iKSkKZGYkbWVzRXNwID0gZmFjdG9yKG1vbnRocyhkZiRzdGFydF9kYXRlKSwgbGV2ZWxzPSBjKCJlbmVybyIsICJmZWJyZXJvIiwgIm1hcnpvIiwgImp1bmlvIiwgImp1bGlvIiwiYWdvc3RvIiwgInNlcHRpZW1icmUiLCAib2N0dWJyZSIsICJub3ZpZW1icmUiLCAiZGljaWVtYnJlIikpCmRmJGRpYUluZyA9IGZhY3Rvcih3ZWVrZGF5cyhkZiRzdGFydF9kYXRlKSwgbGV2ZWxzID0gYygiTW9uZGF5IiwgIlR1ZXNkYXkiLCJXZWRuZXNkYXkiLCAiVGh1cnNkYXkiLCAiRnJpZGF5IiwgIlNhdHVyZGF5IiwiU3VuZGF5IikpCmRmJG1lc0luZyA9IGZhY3Rvcihtb250aHMoZGYkc3RhcnRfZGF0ZSksIGxldmVscz0gYygiSmFudWFyeSIsICJGZWJydWFyeSIsICJNYXJjaCIsIkFwcmlsIiwiTWF5IiwgIkp1bmUiLCAiSnVseSIsIkF1Z3VzdCIsICJTZXB0ZW1iZXIiLCAiT2N0b2JlciIsICJOb3ZlbWJlciIsICJEZWNlbWJlciIpKQoKd2l0aChkZix7CnBhcihtZnJvdz1jKDEsMikpCgpkaWFzID0gdGFibGUoZGlhRXNwKSAjUG9uZXIgZGlhSW5nIG8gZGlhRXNwIHNlZ3VuIGNvcnJlc3BvbmRhCnByb3AudGFibGUodGFibGUoZGlhcykpCmJhcnBsb3QoZGlhcywgbGFzPTIsIGNleC5uYW1lcz0wLjY1KQptZXNlcyA9IHRhYmxlKG1lc0VzcCkgI1BvbmVyIG1lc0luZyBvIG1lc0VzcCBzZWd1biBjb3JyZXNwb25kYQpwcm9wLnRhYmxlKHRhYmxlKG1lc2VzKSkKYmFycGxvdChtZXNlcywgbGFzPTIsIGNleC5uYW1lcz0wLjY1LCApCn0pCmBgYApgYGB7cn0KYWp1c00xPC1sbShwcmljZX4xLGRhdGE9ZGYpCmNvZTwtY29lZihhanVzTTEpCnBsb3QoZGYkcHJpY2UsICB4bGFiID0gIlByb3BpZWRhZGVzIiwgeWxhYiA9ICJQcmVjaW8iKQphYmxpbmUoaCA9IGNvZVsxXSwgY29sPSdyZWQnKQphYmxpbmUoaCA9IHF1YW50aWxlKGRmJHByaWNlLCAwLjk1KSwgY29sPSdibHVlJykKI2ggPSBsbShpZCB+IC0xLCBkYXRhPWRmKQpgYGAKQ29tbyBsYSBsaW5lYSByb2phIHZpZW5lIGRlIHVuIG1vZGVsbyBjb24gdW4gcGFyYW1ldHJvIGNvbnN0YW50ZSBxdWUgbm8gZGVwZW5kZSBkZSBuaW5ndW5hIHZhcmlhYmxlIGV4cGxpY2F0aXZhIGVzIGVsIHByb21lZGlvIChjb24gbG9zcyBmdW5jdGlvbiBzdW1hIGRlIGRpZmVyZW5jaWEgZGUgY3VhZHJhZG9zKS4KUG9kZW1vcyB2ZXIgcXVlIGxhIG1heW9yaWEgZGUgbGFzIHByb3BpZWRhZGVzIChlbCA5NSUpIGVzdGFuIGNvbmNlbnRyYWRhcyBwb3IgZGViYWpvIGRlIGxhIGxpbmVhIGF6dWwuIE1pZW50cmFzIHF1ZSBlbCA1JSBkZSBwcm9waWVkYWRlcyBtYXMgY2FyYXMgZW1wdWphbiBlbCBwcm9tZWRpbyAobGluZWEgcm9qYSkgbGlnZXJhbWVudGUgYXJyaWJhIGRlbCBjZW50cm8gZGUgbGEgbnViZSBuZWdyYSBkZSBwcm9waWVkYWRlcy4gCkVzIGRlY2lyIGVsIDUlIGRlIHByb3BpZWRhZGVzIG1hcyBjYXJhcyBvY3VwYSBtYXMgZGUgbGEgbWl0YWQgZGVsIGdyYWZpY28uIAoKCmBgYHtyfQp3aXRoKGRmLHsKcGFyKG1mcm93PWMoMSwzKSkKICAKcGxvdChwcmljZX5zdXJmYWNlX2NvdmVyZWQsIHhsYWIgPSdTdXBlcmZpY2llIGN1YmllcnRhJywgeWxhYiA9ICdQcmVjaW8nKQpwcmVjaW9TdXBDPC1sbShwcmljZX5zdXJmYWNlX2NvdmVyZWQsZGF0YT1kZikKY29lU1A8LWNvZWYocHJlY2lvU3VwQykKYWJsaW5lKGNvZVNQLCBjb2wgPSAncmVkJykKCnBsb3QocHJpY2V+Zm9uZG8sIHhsYWIgPSdGb25kbycsIHlsYWIgPSAnUHJlY2lvJykKcHJlY2lvRm9uPC1sbShwcmljZX5mb25kbyxkYXRhPWRmKQpjb2VGUDwtY29lZihwcmVjaW9Gb24pCmFibGluZShjb2VGUCwgY29sID0gJ3JlZCcpCgpwbG90KHByaWNlfnN0YXJ0X2RhdGUsIHhsYWIgPSdGZWNoYSBkZSBwdWInLCB5bGFiID0gJ1ByZWNpbycpCnByZWNpb0ZlY2hhPC1sbShwcmljZX5zdGFydF9kYXRlLGRhdGE9ZGYpCmNvZUZlY2g8LWNvZWYocHJlY2lvRmVjaGEpCmFibGluZShjb2VGZWNoLCBjb2wgPSAncmVkJykKfSkKCmBgYApBIGxvcyBkYXRvcyBtZSByZW1pdG8sIGVzIG11Y2hpc2ltbyBtYXMgY2xhcmEgbGEgcmVsYWNpb24gZW50cmUgc3VwZXJmaWNpZSBjdWJpZXJ0YSB5IHByZWNpbywgcXVlIHByZWNpbyB5IGZvbmRvLiAKTmkgcXVlIGhhYmxhciBmZWNoYSBkZSBwdWJsaWNhY2lvbiwgZW4gZWwgY3VhbCBzZSB2ZSBxdWUgbWkgY29lZmljaWVudGUgZGUgZ3JhZG8gMSBlcyBwcmFjdGljYW1lbnRlIDAuIENvbiBsbyBjdWFsIG9idGVuZW1vcyBhbGdvIHNpbWlsYXIgYWwgcHJvbWVkaW8gZGUgZmVjaGEgZGUgcHVibGljYWNpb24uIAoKYGBge3J9CnByZWNpb1N1cEM8LWxtKHByaWNlfnN1cmZhY2VfY292ZXJlZCxkYXRhPWRmKQpwcmVjaW9GZWNoYTwtbG0ocHJpY2V+c3RhcnRfZGF0ZSxkYXRhPWRmKQpwcmVjaW9Gb248LWxtKHByaWNlfmZvbmRvLGRhdGE9ZGYpCgpzdW1tYXJ5KHByZWNpb1N1cEMpCnN1bW1hcnkocHJlY2lvRm9uKQpzdW1tYXJ5KHByZWNpb0ZlY2hhKQoKCmBgYApFbCBSIGRlIGNhZGEgbW9kZWxvIGVzdGEgb3JkZW5hZG8gY29tbyBlbCBncmFmaWNvLiBTdXBlcmYgY3ViID4gZm9uZG8gPiBmZWNoYSBkZSBwdWIKTm90YWRvIHF1ZSBkZSBsb3MgcmVzaWR1b3MsIGVsIG1lZGlhbm8gZXMgc2llbXByZSBuZWdhdGl2bywgcHVlZG8gZGVkdWNpciBxdWUgZWwgbW9kZWxvIGVzdGEgcHJlZGljaWVuZG8gcXVlIGVsIGVsZW1lbnRvIGRlbCBtZWRpbyBkZWJlcmlhIHNlciBtYXMgZ3JhbmRlIHF1ZSBlbCBxdWUgZXMuIEVzdG8gcHVlZGUgZGViZXJzZSBhIGxhIGluZmx1ZW5jaWEgZGUgbG8gbXVjaG8gbWF5b3IgcXVlIGVzIGVsIHByZWNpbyBjdWFuZG8gbGEgc3VwZXJmaWNpZSBlcyBtYXMgZ3JhbmRlLCBsbyBjdWFsIHBvZHJpYSBkZWJlcnNlIGEgcXVlIGVsIG1vZGVsbyBsaW5lYWwgbm8gc2UgYWp1c3RhIGRlbCB0b2RvIGJpZW4gcG9yIGNvbW8gZXN0YW4gZGlzdHJpYnVpZG9zIGxvcyBwcmVjaW9zIGVuIHJlbGFjaW9uIGEgbGEgc3VwLgoKYGBge3J9CnN1bW1hcnkocHJlY2lvU3VwQyRyZXNpZHVhbHMpCnN1bW1hcnkocHJlY2lvRm9uJHJlc2lkdWFscykKc3VtbWFyeShwcmVjaW9GZWNoYSRyZXNpZHVhbHMpCgojc3FydChzdW0oKHByZWNpb1N1cEMkcmVzaWR1YWxzKSoqMikpCmBgYApgYGB7cn0KcGxvdChkZiRzdXJmYWNlX2NvdmVyZWQsIGRmJHByaWNlLCBjb2w9J3JlZCcsIHlsaW0gPSBjKDAgLCAxLjUqbWF4KHByZWRpY3QocHJlY2lvU3VwQykpKSwgeGxhYiA9ICdTdXBlcmZpY2llIGN1YmllcnRhJywgeWxhYiA9ICdQcmVjaW8nKQpwb2ludHMoIGRmJHN1cmZhY2VfY292ZXJlZCwgcHJlZGljdChwcmVjaW9TdXBDKSwgY29sPSdibHVlJykKYGBgCkxhIHByZWRpY2Npb24gZW4gYXp1bCBtaWVudHJhcyBxdWUgbG9zIGRhdG9zIGVuIHJvam8uIFNlIHB1ZWRlIHZlciBjb21vIGxhICdkaXNwZXJzaW9uJyBkZSBsb3MgcHJlY2lvcyBkZSBzdXBlcmZpY2llIG1hcyBiYWphIGVzIG1lbm9yLCBlbnRvbmNlcyB1bm8gcHVlZGUgdGVuZXIgdW5hIG1lam9yIHByZWRpY2Npb24sIG1pZW50cmFzIHF1ZSBzaSBhdW1lbnRhIG11Y2hvIGxhIHN1cGVyZmljaWUgbGEgdmFyaWFiaWxpZGFkIGRlbCBwcmVjaW8gZXMgbWF5b3IsIHkgZWwgbW9kZWxvIGxlIHBpZmlhIG1hcy4gCgoKYGBge3J9CmRlc2VtcGVuaW9IYXN0YVBvcmNlbnRhamUgPSBmdW5jdGlvbihwb3JjZW50YWplKXsKICBzdXAgPSBxdWFudGlsZShkZiRzdXJmYWNlX2NvdmVyZWQsIHBvcmNlbnRhamUpCiAgc3VwcyA9IGRmW2RmJHN1cmZhY2VfY292ZXJlZCA8PSBzdXAsXSRzdXJmYWNlX2NvdmVyZWQKICBwcmVjaW8gPSBkZltkZiRzdXJmYWNlX2NvdmVyZWQgPD0gc3VwLF0kcHJpY2UKICBtb2RlbG88LWxtKHByZWNpb35zdXBzLGRhdGE9ZGYpCiAgcmV0dXJuKHN1bW1hcnkobG0ocHJlY2lvfnN1cHMsIGRhdGE9ZGYpKSRyLnNxdWFyZWQpCiAgCn0KYGBgCmBgYHtyfQpwb3JjZW50YWplcyA9IGMoMToxMDApLzEwMApwbG90KHNhcHBseShwb3JjZW50YWplcywgZGVzZW1wZW5pb0hhc3RhUG9yY2VudGFqZSksIHhsYWIgPSAnUG9yY2VudGFqZSBkZWwgZGF0YXNldCBldmFsdWFkbyBlbiBtb2RlbG8nLCB5bGFiID0gJ0Vycm9yIGRlbCBtb2RlbG8gZW4gUiBjdWFkcmFkbycpCmBgYApDbGFyYW1lbnRlIGVsIGVycm9yIGNyZWNlIHJhb2lkYW1lbnRlIHkgImRlIGEgc2FsdG9zIiBhIG1lZGlkYSBxdWUgdG9tbyB1biBtYXlvciBwb3JjZW50YWplIGRlIGxvcyBkYXRvcy4KCgpgYGB7cn0KcGFyKG1mcm93PWMoMSwzKSkKcGxvdChwcmVjaW9TdXBDJHJlc2lkdWFscywgeGxhYiA9ICdSZXNpZHVvcyBzdXBlcmZpY2llIGN1YmllcnRhJywgeWxhYiA9ICdSZXNpZHVvcycpCmFibGluZShoPTAsY29sPSAncmVkJykKcGxvdChwcmVjaW9Gb24kcmVzaWR1YWxzLCB4bGFiID0gJ1Jlc2lkdW9zIGZvbmRvJywgeWxhYiA9ICdSZXNpZHVvcycpCmFibGluZShoPTAsY29sPSAncmVkJykKcGxvdChwcmVjaW9GZWNoYSRyZXNpZHVhbHMsIHhsYWIgPSAnUmVzaWR1b3MgZmVjaGEgZGUgcHViJywgeWxhYiA9ICdSZXNpZHVvcycpCmFibGluZShoPTAsY29sPSAncmVkJykKYGBgClRhbCBjb21vIGVzcGVyYWJhbW9zLCBlbCBwcm9tZWRpbyBkZWwgbW9kdWxvIGxvcyByZXNpZHVvcyBlcyBtYXMgY2hpY28gZW4gc3VwZXJpZmljZSBjdWJpZXJ0YSAoZ3JhZmljbyBtYXMgY29tcHJpbWlkbyBlbiAwKSBtaWVudHJhcyBxdWUgdmEgYXVtZW50YW5kbyBkZWJpZG8gYSBtYXlvcmVzIGRpZmVyZW5jaWFzIGFsIDAgZW4gbG9zIG90cm9zIGdyYWZpY29zLiAKCmBgYHtyfQptb2RlbG9QcmVjaW9UaWVtcG8gPSBsbShwcmljZX5zdGFydF9kYXRlLCBkYXRhID0gZGYpCnBsb3QoZGYkcHJpY2V+ZGYkc3RhcnRfZGF0ZSwgeGxhYj0iRmVjaGEiLCB5bGFiPSJQcmVjaW8iKQphYmxpbmUoY29lZihtb2RlbG9QcmVjaW9UaWVtcG8pLCBjb2w9J3JlZCcpCmBgYApObyBzZSBhcHJlY2lhIGxhIGluZmxhY2lvbiwgZXMgcHJhY3RpY2FtZW50ZSB1bmEgY29uc3RhbnRlLgoKYGBge3J9Cm1vZGVsb1ByZWNpb1RpcG9Qcm9waWVkYWQgPSBsbShwcmljZX5wcm9wZXJ0eV90eXBlLCBkYXRhID0gZGYpCmJveHBsb3QoZGYkcHJpY2V+ZGYkcHJvcGVydHlfdHlwZSkKY29lZlByb3AgPSBjb2VmKG1vZGVsb1ByZWNpb1RpcG9Qcm9waWVkYWQpCmFibGluZShtb2RlbG9QcmVjaW9UaXBvUHJvcGllZGFkKQpwcmludChjb2VmUHJvcCkKYGBgCkxvIHF1ZSBlc3RlIG1vZGVsbyBub3MgZGljZSBlcyBxdWUgbGFzIGNhc2FzIHBvciBlbCBzaW1wbGUgaGVjaG8gZGUgc2VyIGNhc2FzIHRpZW5lbiB1biB2YWxvciBwcm9tZWRpbyBkZSAyMjk4MSAocXVlIGFjdHVhIGNvbW8gdW5hICdiYXNlJyBwYXJhIGVsIHJlc3RvIGRlIGxhcyBwcm9waWVkYWRlcy4gCk1pZW50cmFzIHF1ZSBzZXIgdW4gZGVwYXJ0YW1lbnRvIHRlIHBlbmFsaXphIHBvciAtNTg1MiBtZW5vcyBlbCBwcmVjaW8gcmVsYXRpdm8gYSB1bmEgY2FzYSB5IHNlciB1biBQSCB0YW1iaWVuIHRlIHBlbmFsaXphIHBvciAtMzEwMy4gCkVsIG1vZGVsbyBxdWUgaGljZSBzb2xhbWVudGUgY29uZWN0YSBwcmVjaW8gZGUgQ2FzYSB2cyBEZXBhcnRhbWVudG8KCgpgYGB7cn0Kem9uYXMgPSBrbWVhbnMoZGYkbGF0ICsgZGYkbG9uLCAzLCBpdGVyLm1heCA9IDUwKQpgYGAKYGBge3J9CmNvbG9yZXMgPSBjKCdyZWQnLCdncmVlbicsICdibHVlJykKcGxvdChkZiRsYXQsIGRmJGxvbiwgY29sPWNvbG9yZXNbem9uYXMkY2x1c3Rlcl0pCmBgYAoKCkRpdmlkaW1vcyBwb3Igem9uYSBzZWd1biB1YmljYWNpb24sIHkgbm9zIGZpamFtb3MgcXVlIG1vZGVsbyBhanVzdGEgbWVqb3IgYSBjYWRhIHpvbmEgcGFydGljdWxhcgpgYGB7cn0KCm1vZGVsbzEgPSBsbShwcmljZX5zdXJmYWNlX2NvdmVyZWQrcm9vbXMsIGRhdGEgPSBkZlt6b25hcyRjbHVzdGVyPT0xLF0pCm1vZGVsbzIgPSBsbShwcmljZX5zdXJmYWNlX2NvdmVyZWQrcm9vbXMsIGRhdGEgPSBkZlt6b25hcyRjbHVzdGVyPT0yLF0pCm1vZGVsbzMgPSBsbShwcmljZX5zdXJmYWNlX2NvdmVyZWQrcm9vbXMsIGRhdGEgPSBkZlt6b25hcyRjbHVzdGVyPT0zLF0pCgpwYXIobWZyb3c9YygyLDMpKQpwbG90KGRmW3pvbmFzJGNsdXN0ZXI9PTEsXSRsYXQsIGRmW3pvbmFzJGNsdXN0ZXI9PTEsXSRsb24sIGNvbD1jb2xvcmVzWzFdKQpwbG90KGRmW3pvbmFzJGNsdXN0ZXI9PTIsXSRsYXQsIGRmW3pvbmFzJGNsdXN0ZXI9PTIsXSRsb24sIGNvbD1jb2xvcmVzWzJdKQpwbG90KGRmW3pvbmFzJGNsdXN0ZXI9PTMsXSRsYXQsIGRmW3pvbmFzJGNsdXN0ZXI9PTMsXSRsb24sIGNvbD1jb2xvcmVzWzNdKQpzY2F0dGVyM0QoZGZbem9uYXMkY2x1c3Rlcj09MSxdJHN1cmZhY2VfY292ZXJlZCwgZGZbem9uYXMkY2x1c3Rlcj09MSxdJHJvb21zLCBkZlt6b25hcyRjbHVzdGVyPT0xLF0kcHJpY2UsIGNvbHZhciA9IGRmW3pvbmFzJGNsdXN0ZXI9PTEsXSRwcmljZSwgY29sID0gMiwgYWRkID0gRix4bGFiID0gJ1N1cCBDdWInLCB5bGFiPSAnSGFiJywgemxhYiA9ICdQcmVjaW8nKQpzY2F0dGVyM0QoZGZbem9uYXMkY2x1c3Rlcj09MixdJHN1cmZhY2VfY292ZXJlZCwgZGZbem9uYXMkY2x1c3Rlcj09MixdJHJvb21zLCBkZlt6b25hcyRjbHVzdGVyPT0yLF0kcHJpY2UsIGNvbHZhciA9IGRmW3pvbmFzJGNsdXN0ZXI9PTIsXSRwcmljZSwgY29sID0gMywgYWRkID0gRix4bGFiID0gJ1N1cCBDdWInLCB5bGFiPSAnSGFiJywgemxhYiA9ICdQcmVjaW8nKQpzY2F0dGVyM0QoZGZbem9uYXMkY2x1c3Rlcj09MyxdJHN1cmZhY2VfY292ZXJlZCwgZGZbem9uYXMkY2x1c3Rlcj09MyxdJHJvb21zLCBkZlt6b25hcyRjbHVzdGVyPT0zLF0kcHJpY2UsIGNvbHZhciA9IGRmW3pvbmFzJGNsdXN0ZXI9PTMsXSRwcmljZSwgY29sID0gNCwgYWRkID0gRiwgeGxhYiA9ICdTdXAgQ3ViJywgeWxhYj0gJ0hhYicsIHpsYWIgPSAnUHJlY2lvJykKI3NldC5zZWVkKDQxNykKI3RlbXAgPC0gcm5vcm0oMTAwLCBtZWFuPTMwLCBzZD01KQojcHJlc3N1cmUgPC0gcm5vcm0oMTAwKQojZHRpbWUgPC0gMToxMDAKI3Bsb3RfbHkoeD1kZlt6b25hcyRjbHVzdGVyPT0xLF0kc3VyZmFjZV9jb3ZlcmVkLCB5PWRmW3pvbmFzJGNsdXN0ZXI9PTEsXSRyb29tcywgej1kZlt6b25hcyRjbHVzdGVyPT0xLF0kcHJpY2UsIHR5cGU9InNjYXR0ZXIzZCIsIG1vZGU9Im1hcmtlcnMiLCBjb2xvcj0wLjMpCgpgYGAKYGBge3J9CgpTdXBlcmZpY2llID0gZGYkc3VyZmFjZV9jb3ZlcmVkCkhhYml0YWNpb25lcyA9IGRmJHJvb21zClByZWNpbyA9IGRmJHByaWNlCmNvbG9yZXMyID0gYygiSW5kaWFuUmVkIiwgIk1lZGl1bVB1cnBsZSIsICJEYXJrT3JhbmdlIikKI2NvbG9yPWNvbG9yZXNbem9uYXMkY2x1c3Rlcl0KCmZpZyA8LSBwbG90X2x5KHg9flN1cGVyZmljaWUsIHk9fkhhYml0YWNpb25lcywgej1+UHJlY2lvLCB0eXBlPSJzY2F0dGVyM2QiLCBtb2RlPSJtYXJrZXJzIiwgbWFya2VyID0gbGlzdChjb2xvciA9IGNvbG9yZXNbem9uYXMkY2x1c3Rlcl0sIHNob3dzY2FsZSA9IEYpLHhsYWIgPSAnU3VwIEN1YicsIHlsYWI9ICdIYWInLCB6bGFiID0gJ1ByZWNpbycsIHNpemUgPSAxKQpmaWcgPC0gZmlnICU+JSBsYXlvdXQodGl0bGUgPSAnMyB6b25hcyBkaXN0cmlidWljaW9uJywKICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gJ1N1cGVyZmljaWUgY3ViaWVydGEnLAogICAgICAgICAgICAgICAgICAgICAgemVyb2xpbmUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgcmFuZ2UgPSBjKDAsIDI1MCkpLAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAnSGFiaXRhY2lvbmVzJywKICAgICAgICAgICAgICAgICAgICAgIHJhbmdlID0gYygwLDE0MDApKSkKCmZpZwoKYGBgCgoKYGBge3J9Cm1vZGVsbzEkY29lZmZpY2llbnRzCnBsb3QzZChtb2RlbG8xLHNpemU9MTUsY29sPSJyZWQiKQoKYGBgCmBgYHtyfQptb2RlbG8yJGNvZWZmaWNpZW50cwoKcGxvdDNkKG1vZGVsbzIsc2l6ZT0xNSxjb2w9ImdyZWVuIikKYGBgCgpgYGB7cn0KbW9kZWxvMyRjb2VmZmljaWVudHMKCnBsb3QzZChtb2RlbG8zLHNpemU9MTUsY29sPSJibHVlIikKYGBgCkRlIGxvcyBjb2VmaWNpZW50ZXMgZGUgY2FkYSBtb2RlbG8gc2VndW4gbGEgem9uYSBwb2RlbW9zIHZlciBxdWUgbGFzIGhhYml0YWNpb25lcyBzdW1hbiBtdWNobyBtYXMgZW4gdW5hIGRlIGxhcyAzIHpvbmFzLiAKUG9yIG90cm8gbGFkbyBoYXkgb3RyYSB6b25hIGRpc3RpbnRhIHF1ZSBlcyBtYXMgY2FyYSBhICcwIGhhYiB5IDAgc3VwZXJmaWNpZScuIApFbiBjdWFudG8gYSBjdWFudG8gY3Vlc3RhIGVsIG0yIGN1YWRyYWRvIGN1YmllcnRvIGVuIGNhZGEgem9uYSBlcyBzaW1pbGFyLgoKCkNvbnN0cnV5YSB1bmEgZ3LDoWZpY2EgcXVlIG11ZXN0cmUgZWwgZXJyb3IgZGUgYWp1c3RlIGVuCmZ1bmNpw7NuIGRlIGxhIGNhbnRpZGFkIGRlIHBhcsOhbWV0cm9zIGRlIGNhZGEgbW9kZWxvLiBDdcOhbCBtb2RlbG8gdGllbmUgbWVub3IgZXJyb3IgZGUgYWp1c3RlPwpgYGB7cn0KbW9kZWxvU2ltcGxlMCA9IGxtKHByaWNlfnN1cmZhY2VfY292ZXJlZCArIGZvbmRvLCBkYXRhID0gZGYpCm1vZGVsb1NpbXBsZTEgPSBsbShwcmljZX5zdXJmYWNlX2NvdmVyZWQgK2ZvbmRvLCBkYXRhID0gZGYpCm1vZGVsb1NpbXBsZTIgPSBsbShwcmljZX5zdXJmYWNlX2NvdmVyZWQgK2ZvbmRvK3Byb3BlcnR5X3R5cGUsIGRhdGEgPSBkZikKbW9kZWxvU2ltcGxlMyA9IGxtKHByaWNlfnN1cmZhY2VfY292ZXJlZCArZm9uZG8rcHJvcGVydHlfdHlwZStsYXQrbG9uLCBkYXRhID0gZGYpCm1vZGVsb1NpbXBsZTQgPSBsbShwcmljZX5zdXJmYWNlX2NvdmVyZWQgK2ZvbmRvK3Byb3BlcnR5X3R5cGUrbGF0K2xvbitzdGFydF9kYXRlLCBkYXRhID0gZGYpCm1vZGVsb0NvbXBsZXRvID0gbG0ocHJpY2V+c3VyZmFjZV9jb3ZlcmVkICtmb25kbytwcm9wZXJ0eV90eXBlK2xhdCtsb24rc3RhcnRfZGF0ZStyb29tcywgZGF0YSA9IGRmKQpjb2VmUiA9IGMoc3VtbWFyeShtb2RlbG9TaW1wbGUwKSRyLnNxdWFyZWQsc3VtbWFyeShtb2RlbG9TaW1wbGUxKSRyLnNxdWFyZWQsc3VtbWFyeShtb2RlbG9TaW1wbGUyKSRyLnNxdWFyZWQsc3VtbWFyeShtb2RlbG9TaW1wbGUzKSRyLnNxdWFyZWQsc3VtbWFyeShtb2RlbG9TaW1wbGU0KSRyLnNxdWFyZWQsc3VtbWFyeShtb2RlbG9Db21wbGV0bykkci5zcXVhcmVkKQoKYGBgCgpgYGB7cn0KcGxvdChjKDE6NiksIGNvZWZSLCB4bGFiID0gJ01vZGVsbycsIHlsYWI9JyBDb2VmaWNpZW50ZSBSIGN1YWRyYWRvJykKYGBgClBvZGVtb3MgdmVyIGNvbW8gcHJvZ3Jlc2EgZWwgUiBjdWFkcmFkbyBjdWFuZG8gdmFtb3Mgc3VtYW5kbyB2YXJpYWJsZXMgZXhwbGljYXRpdmFzLCBub3Rlc2UgY29tbyBzdW1hciBsYSBmZWNoYSBkZSBwdWJsaWNhY2lvbiB5IGhhYml0YWNpb25lcyBubyBjYW1iaWEgcHJhY3RpY2FtZW50ZSBuYWRhLCB5YSBxdWUgNCB5IDUgeSA2IHNvbiBjYXNpIGlkZW50aWNvcy4KTG8gbWlzbW8gY29uIDEsIDIgeSAzLCBwcm9wZXJ0eSB0eXBlIHkgZm9uZG8gbm8gcmVkdWplcm9uIG11Y2hvIGVsIGVycm9yIGRlbCBtb2RlbG8uCkVzdG8gbm9zIGRpY2UgcXVlIHBhcmEgbWluaW1pemFyIGVsIGVycm9yIGRlIGFqdXN0ZSBoYXkgcXVlIHN1bWFyIHZhcmlhYmxlcyBleHBsaWNhdGl2YXMsIHBlcm8gbm8gY3VhbHF1aWVyYSwgeWEgcXVlIGVuIGVzdGUgY2FzbyBsYSBmZWNoYSBmdWUgdW4gcGVzaW1vIGluZGljYWRvci4KRWwgcXVpZWJyZSBzZSBwcm9kdWNlIGN1YW5kbyBjb25zaWRlcmFtb3MgbGF0aXR1ZCB5IGxvbmdpdHVkLgoK